/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.zhiqim.git.objer.ObjectLoader;
import org.zhiqim.git.objer.model.Tree;
import org.zhiqim.git.objer.model.TreeEntry;
import org.zhiqim.git.objer.objectid.ObjectId;
import org.zhiqim.git.objer.objectid.RevBlob;
import org.zhiqim.git.objer.objectid.RevCommit;
import org.zhiqim.git.objer.objectid.RevObject;
import org.zhiqim.git.objer.objectid.RevTag;
import org.zhiqim.git.objer.objectid.RevTree;
import org.zhiqim.kernel.annotation.AnNonnull;
import org.zhiqim.kernel.annotation.AnNullable;
import org.zhiqim.kernel.util.Asserts;

/**
 * Git步行器，用于线程内处理时对象暂存
 *
 * @version v1.0.0 @author zouzhigang 2017-4-25 新建与整理
 */
public class GitWalker implements GitConstants, AutoCloseable
{
    protected final Git git;
    protected final List<RevObject> objects;
    protected boolean retainBody;
    
    public GitWalker(Git git)
    {
        this.git = git;
        this.objects = new ArrayList<RevObject>();
        this.retainBody = true;
    }
    
    public void close()
    {
        objects.clear();
    }
    
    /*********************************************************************/
    //参数设置&获取
    /*********************************************************************/
    
    public Git git()
    {
        return git;
    }
    
    public boolean isRetainBody()
    {
        return retainBody;
    }
    
    public void setRetainBody(boolean retainBody)
    {
        this.retainBody = retainBody;
    }
    
    /*********************************************************************/
    //对象查询
    /*********************************************************************/
    
    public RevObject lookup(ObjectId id, int type)
    {
        int ind = objects.indexOf(id);
        if (ind != -1)
            return objects.get(ind);
        
        RevObject r = null;
        switch (type)
        {
        case OBJ_COMMIT:
            r = new RevCommit(id);
            break;
        case OBJ_TREE:
            r = new RevTree(id);
            break;
        case OBJ_BLOB:
            r = new RevBlob(id);
            break;
        case OBJ_TAG:
            r = new RevTag(id);
            break;
        default:
            throw Asserts.exception(GitI18n.illegalObjectType2, id, type);
        }
        objects.add(r);
        return r;
    }
    
    public RevBlob lookupBlob(ObjectId id)
    {
        int ind = objects.indexOf(id);
        if (ind != -1)
            return (RevBlob)objects.get(ind);
        
        RevBlob c = new RevBlob(id);
        objects.add(c);
        return c;
    }

    public RevTree lookupTree(ObjectId id)
    {
        int ind = objects.indexOf(id);
        if (ind != -1)
            return (RevTree) objects.get(ind);
        
        RevTree c = new RevTree(id);
        objects.add(c);
        return c;
    }

    public RevCommit lookupCommit(ObjectId id)
    {
        int ind = objects.indexOf(id);
        if (ind != -1)
            return (RevCommit) objects.get(ind);
        
        RevCommit c = new RevCommit(id);
        objects.add(c);
        return c;
    }

    /*********************************************************************/
    //对象解析
    /*********************************************************************/

    @AnNonnull /** 解析对象 */
    public RevObject parse(ObjectId id) throws IOException
    {
        int ind = objects.indexOf(id);
        if (ind != -1)
        {
            RevObject r = objects.get(ind);
            parseHeaders(r);
            return r;
        }

        ObjectLoader ldr = git.getObjectLoader(id);
        Asserts.assertNotNull(ldr, GitI18n.objectNotFound, id);
        
        RevObject r = null;
        int type = ldr.getType();
        switch (type)
        {
        case OBJ_COMMIT:
        {//提交解析整个数据
            RevCommit c = new RevCommit(id);
            c.parseCanonical(this, ldr.getData());
            r = c;
            break;
        }
        case OBJ_TREE:
        {//目录解析头部
            r = new RevTree(id);
            r.parseHeaders(this);
            r.setParsed();
            break;
        }
        case OBJ_BLOB:
        {//文件不解析数据
            r = new RevBlob(id);
            r.setParsed();
            break;
        }
        case OBJ_TAG:
        {//标签解析整个数据
            RevTag t = new RevTag(id);
            t.parseCanonical(this, ldr.getData());
            r = t;
            break;
        }
        default:
            throw Asserts.exception(GitI18n.illegalObjectType2, id, type);
        }
        
        objects.add(r);
        return r;
    }
    
    @AnNullable /** 解析对象，不存在为null*/
    public RevObject parseNullable(ObjectId id) throws IOException
    {
        try
        {
            return id != null?parse(id):null;
        }
        catch (NullPointerException e)
        {
            return null;
        }
    }
    
    @AnNullable /** 指定解析提交，不存在或无法解析成提交的返回null */
    public RevCommit parseCommitNullable(ObjectId id) throws IOException
    {
        RevObject c = parseNullable(id);
        if (c == null)
            return null;
        
        while (c instanceof RevTag)
        {
            c = ((RevTag)c).getObject();
            parseHeaders(c);
        }
        
        if (!(c instanceof RevCommit))
            return null;
        
        return (RevCommit)c;
    }
    
    /** 指定解析提交 */
    public RevCommit parseCommit(ObjectId id) throws IOException
    {
        RevObject c = parse(id);
        while (c instanceof RevTag)
        {
            c = ((RevTag)c).getObject();
            parseHeaders(c);
        }
        
        if (!(c instanceof RevCommit))
            throw Asserts.exception(id.toString() + "不是提交类型");
        
        return (RevCommit)c;
    }
    
    public RevBlob parseBlob(ObjectId id) throws IOException
    {
        RevObject c = parse(id);
        while (c instanceof RevTag)
        {
            c = ((RevTag)c).getObject();
            parseHeaders(c);
        }
        
        if (!(c instanceof RevBlob))
            throw Asserts.exception(id.toString() + "不是文件类型");
        
        return (RevBlob)c;
    }

    public RevTree parseTree(ObjectId id) throws IOException
    {
        RevObject c = parse(id);
        while (c instanceof RevTag)
        {
            c = ((RevTag)c).getObject();
            parseHeaders(c);
        }

        RevTree t;
        if (c instanceof RevCommit){
            t = ((RevCommit)c).getTree();
        }else if (!(c instanceof RevTree)){
            throw Asserts.exception(id.toString() + "不是树类型");
        }else{
            t = (RevTree)c;
        }
        
        parseHeaders(t);
        return t;
    }
    
    /*********************************************************************/
    //对象解析头&主体&剥除
    /*********************************************************************/

    public void parseHeaders(RevObject obj) throws IOException
    {
        if (!obj.isParsed())
            obj.parseHeaders(this);
    }

    public void parseBody(RevObject obj) throws IOException
    {
        obj.parseBody(this);
    }
    
    public RevObject peel(RevObject obj) throws IOException
    {
        while (obj instanceof RevTag)
        {
            parseHeaders(obj);
            obj = ((RevTag) obj).getObject();
        }
        
        parseHeaders(obj);
        return obj;
    }
    
    /*********************************************************************/
    //判断是否提交是当前的快进
    /*********************************************************************/
    
    /**
     * 判断提交是否当前提交的快进（base是否是tip的祖先提交）
     * 
     * @param base          基础提交，必须在提交树中
     * @param tip           子提交，要提交的且不在提交树中
     * @return              ＝true表示可以从base快进到tip
     * @throws IOException  异常
     */
    public boolean isFastward(RevCommit base, RevCommit tip) throws IOException
    {
        //1.设置查询队列和已查列表
        LinkedList<RevCommit> queue = new LinkedList<>();
        queue.addLast(tip);
        
        HashSet<RevCommit> searched = new HashSet<>();
        
        //2.循环查找是否能找到base
        while (true)
        {
            RevCommit commit = queue.pollFirst();
            if (commit == null)
            {//2.1 搜索结束，最终返回false
                return false;
            }
            
            if (!commit.hasParents())
            {//2.2 搜索到根节点，不再搜索
                continue;
            }
            
            if (searched.contains(commit))
            {//2.3 搜索到重复的，过滤掉
                continue;
            }
            
            //2.4 从父提交中查找
            for (RevCommit parent : commit.getParents())
            {
                if (ObjectId.equals(parent, base))
                    return true;
                
                if (!parent.isParsed())
                    parent.parseHeaders(this);
                
                queue.addLast(parent);
            }
            
            //2.5 把已查找的加入到已查找列表，防止重复查找
            searched.add(commit);
        }
    }
    
    /**
     * 判断提交是否当前提交的快进（base是否是tip的祖先提交）
     * 
     * @param base          基础提交，必须在提交树中
     * @param tip           子提交，要提交的且不在提交树中
     * @param dept          基础提交深度的提交
     * @return              ＝true表示可以从base快进到tip
     * @throws IOException  异常
     */
    public RevCommit getFastward(RevCommit base, RevCommit tip, int dept) throws IOException
    {
        //1.设置查询队列和已查列表
        LinkedList<RevCommit> queue = new LinkedList<>();
        queue.addLast(tip);
        
        LinkedList<RevCommit> searched = new LinkedList<>();
        
        //2.循环查找是否能找到base
        w:while (true)
        {
            RevCommit commit = queue.pollFirst();
            if (commit == null)
            {//2.1 搜索结束，最终返回false
                return null;
            }
            
            if (!commit.hasParents())
            {//2.2 搜索到根节点，不再搜索
                continue;
            }
            
            if (searched.contains(commit))
            {//2.3 搜索到重复的，过滤掉
                continue;
            }
            
            //2.4 从父提交中查找
            for (RevCommit parent : commit.getParents())
            {
                if (ObjectId.equals(parent, base))
                {//找到了，把最后一个放进去
                    searched.add(commit);
                    break w;
                }
                
                if (!parent.isParsed())
                    parent.parseHeaders(this);
                
                queue.addLast(parent);
            }
            
            //2.5 把已查找的加入到已查找列表，防止重复查找
            searched.add(commit);
        }
        
        //3. 找到了，则取最后放入的第dept个
        int ind = (dept >= searched.size())?0:searched.size()-dept;
        return searched.get(ind);
    }
    
    /**
     * 判断提交是否当前提交的快进（base是否是tip的祖先提交）
     * 
     * @param tip           子提交，要提交的且不在提交树中
     * @param dept          基础提交深度的提交
     * @return              ＝true表示可以从base快进到tip
     * @throws IOException  异常
     */
    public RevCommit getFastward(RevCommit tip, int dept) throws IOException
    {
        //1.设置查询队列和已查列表
        LinkedList<RevCommit> queue = new LinkedList<>();
        queue.addLast(tip);
        
        LinkedList<RevCommit> searched = new LinkedList<>();
        
        //2.循环查找是否能找到base
        while (true)
        {
            RevCommit commit = queue.pollFirst();
            if (commit == null)
            {//2.1 搜索结束，最终返回false
                return null;
            }
            
            if (searched.contains(commit))
            {//2.2 搜索到重复的，过滤掉
                continue;
            }
            
            if (!commit.hasParents())
            {//2.3 搜索到根节点，不再搜索，最后一个
                searched.add(commit);
                break;
            }
            
            //2.4 从父提交中查找
            for (RevCommit parent : commit.getParents())
            {
                if (!parent.isParsed())
                    parent.parseHeaders(this);
                
                queue.addLast(parent);
            }
            
            //2.6 把已查找的加入到已查找列表，防止重复查找
            searched.add(commit);
        }
        
        //3. 找到了，则取最后放入的第dept个
        int ind = (dept >= searched.size())?0:searched.size()-dept;
        return searched.get(ind);
    }
    
    /**
     * 通过要求的提交列表，循环提交树获取所有到last为止的提交列表
     * 
     * @param queue         要求的列表
     * @throws IOException  异常
     */
    public List<RevCommit> getWantCommitList(LinkedList<RevCommit> queue) throws IOException
    {
        List<RevCommit> list = new ArrayList<>();
        
        while (true)
        {
            RevCommit commit = queue.pollFirst();
            if (commit == null)
            {//队列里没有要求的退出
                return list;
            }
            
            if (commit.isHave() || commit.isExisted())
            {//客户端已有了，或服务端已增加existed标志的，不再检查父提交
                continue;
            }
            
            //设置服务端existed标志，放置到结果列表，并检查父提交
            commit.setExisted();
            list.add(commit);
            
            if (!commit.hasParents())
            {//没有父提交的，不再处理
                continue;
            }
            
            for (RevCommit parent : commit.getParents())
            {
                parent = parseCommit(parent);
                if (parent.isHave() || parent.isExisted())
                {//父提交客户端已有，或服务端已增加existed标志的，不再检查
                    continue;
                }
                
                //把需要检查的父提交，放回到队列中循环操作
                queue.addFirst(parent);
            }
        }
    }
    
    /**
     * 通过提交列表，获取所有的提交对应的树的对象列表
     * 
     * @param list              对象结果
     * @param wantCommitList    需要的提交列表
     * @param haveCommitList    已有的提交列表
     * @throws IOException      异常
     */
    public void list(List<RevObject> list, List<RevCommit> wantCommitList, Set<RevCommit> haveCommitList) throws IOException
    {
        for (RevCommit commit : haveCommitList)
        {
            RevTree haveTree = commit.getTree();
            haveTree = parseTree(haveTree);
            haveTree.setHave();
            
            Tree tree = haveTree.getTree();
            listTreeHave(tree);
        }
        
        for (RevCommit commit : wantCommitList)
        {
            RevTree wantTree = commit.getTree();
            wantTree = parseTree(wantTree);
            if (wantTree.isHave() || wantTree.isExisted())
                continue;
            
            wantTree.setExisted();
            list.add(wantTree);
            
            Tree tree = wantTree.getTree();
            listTree(list, tree);
        }
    }
    
    /** 递归读取指定目录下的对象 */
    private void listTree(List<RevObject> list, Tree tree) throws IOException
    {
        for (TreeEntry file : tree.getFileList())
        {
            if (file.getMode() == MODE_GITLINK || file.getMode() == MODE_SYMLINK)
            {//TODO 暂不支持这两种LINK
                continue;
            }
            
            RevBlob blob = parseBlob(file.getOid());
            if (blob.isHave() || blob.isExisted())
                continue;
            
            blob.setExisted();
            list.add(blob);
        }
        
        for (TreeEntry folder : tree.getFolderList())
        {
            RevTree revTree = parseTree(folder.getOid());
            if (revTree.isHave() || revTree.isExisted())
                continue;
            
            revTree.setExisted();
            list.add(revTree);
            
            Tree subTree = revTree.getTree();
            listTree(list, subTree);
        }
    }
    
    /** 递归读取指定目录下的对象 */
    private void listTreeHave(Tree tree) throws IOException
    {
        for (TreeEntry file : tree.getFileList())
        {
            RevBlob blob = parseBlob(file.getOid());
            blob.setHave();
        }
        
        for (TreeEntry folder : tree.getFolderList())
        {
            RevTree revTree = parseTree(folder.getOid());
            revTree.setHave();
            
            Tree subTree = revTree.getTree();
            listTreeHave(subTree);
        }
    }
}
